package com.yami.shop.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.aliyuncs.CommonRequest;
import com.aliyuncs.CommonResponse;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.http.MethodType;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.profile.IClientProfile;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yami.shop.bean.enums.SendType;
import com.yami.shop.bean.model.NotifyTemplate;
import com.yami.shop.bean.model.SmsLog;
import com.yami.shop.bean.model.User;
import com.yami.shop.common.bean.ALiDaYu;
import com.yami.shop.common.config.Constant;
import com.yami.shop.common.exception.YamiShopBindException;
import com.yami.shop.common.util.Json;
import com.yami.shop.common.util.RedisUtil;
import com.yami.shop.config.ShopConfig;
import com.yami.shop.dao.NotifyTemplateMapper;
import com.yami.shop.dao.SmsLogMapper;
import com.yami.shop.service.SmsLogService;
import com.yami.shop.service.UserService;
import com.yami.shop.smsserver.SmsServer;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.AntPathMatcher;

import java.util.*;
import java.util.Map.Entry;


/**
 * @author lgh on 2018/11/29.
 */
@Service
@Slf4j
@AllArgsConstructor
public class SmsLogServiceImpl extends ServiceImpl<SmsLogMapper, SmsLog> implements SmsLogService {

    private SmsLogMapper smsLogMapper;

    private ShopConfig shopConfig;
    private final UserService userService;
    private final NotifyTemplateMapper notifyTemplateMapper;
    private SmsServer tenecntSmsServerImpl;
    private SmsServer aliDayuSmsServerImpl;
    /**
     * 产品域名,开发者无需替换
     */
    private static final String DOMAIN = "dysmsapi.aliyuncs.com";
    /**
     * 产品RegionId,开发者无需替换
     */
    private static final String REGION_ID = "cn-hangzhou";

    /**
     * 产品version,开发者无需替换
     */
    private static final String VERSION = "2017-05-25";

    /**
     * 产品Action,开发者无需替换
     */
    private static final String ACTION = "SendSms";

    /**
     * 当天最大验证码短信发送量
     */
    private static final int TODAY_MAX_SEND_VALID_SMS_NUMBER = 10;

    /**
     * 一段时间内短信验证码的最大验证次数
     */
    private static final int TIMES_CHECK_VALID_CODE_NUM = 10;

    /**
     * 短信验证码的前缀
     */
    private static final String CHECK_VALID_CODE_NUM_PREFIX = "checkValidCodeNum_";

    /**
     * 短信验证码的前缀
     */
    private static final String CHECK_VALID_CODE_LOCK_PREFIX = "checkValidLock_";

    /**
     * 短信列表的大小/列表的索引
     */
    private static final Integer INDEX = 0;

    private RedissonClient redissonClient;

    @Override
    public void sendSms(SendType sendType, String userId, String mobile, Map<String, String> params) {
        // 非登录的验证码，非注册的验证码，非用户校验的验证码，都需要用户存在才行
        if (!Objects.equals(sendType,SendType.REGISTER) && !Objects.equals(sendType,SendType.VALID) && !Objects.equals(sendType,SendType.LOGIN)){
            User user = userService.getOne(new QueryWrapper<User>().lambda().eq(User::getUserMobile, mobile));
            if (Objects.isNull(user)) {
                // 用户不存在
                throw new YamiShopBindException("yami.user.account.no.exist");
            }
            if (user.getStatus() == 0){
                // 用户已禁用，请联系客服
                throw new YamiShopBindException("yami.user.disabled");
            }
        }
        RLock lock = redissonClient.getLock(CHECK_VALID_CODE_LOCK_PREFIX + mobile);
        try {
            lock.lock();
            // 校验并发送短信
            SmsLogService smsLogService = (SmsLogService) AopContext.currentProxy();
            Boolean flag = smsLogService.checkAndSendCode(sendType, userId, mobile, params);
            if(!flag){
                // 发送短信失败，请稍后再试
                throw new YamiShopBindException("yami.sending.message.failed");
            }
        } finally {
            lock.unlock();
        }

    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean checkAndSendCode(SendType sendType, String userId, String mobile, Map<String, String> params) {
        // 校验每日短信数量，以及1分钟内不可重复发送 新类型 添加 判断条件
        checkValidSendCount(sendType, userId, mobile, params);
        NotifyTemplate notifyTemplate = notifyTemplateMapper.selectOne(new LambdaQueryWrapper<NotifyTemplate>()
                .eq(NotifyTemplate::getSendType, sendType.getValue()));
        SmsLog smsLog = new SmsLog();
        smsLog.setType(sendType.getValue());
        smsLog.setMobileCode(params.get("code"));
        smsLog.setRecDate(new Date());
        smsLog.setStatus(1);
        smsLog.setUserId(userId);
        smsLog.setUserPhone(mobile);
        smsLog.setContent(formatContent(notifyTemplate.getMessage(), params));
        smsLogMapper.insert(smsLog);
        // 发送短信
        return this.sendMsgSms(mobile, notifyTemplate.getTemplateCode(), params);
    }

    private void checkValidSendCount(SendType sendType, String userId, String mobile, Map<String, String> params) {
        boolean isValidCode = Objects.equals(SendType.REGISTER, sendType) || Objects.equals(SendType.LOGIN, sendType)
                || Objects.equals(SendType.UPDATE_PASSWORD, sendType) || Objects.equals(SendType.VALID, sendType )|| Objects.equals(SendType.SET_PAY_PASSWORD, sendType)|| Objects.equals(SendType.CANCEL, sendType) ;
        // 验证码类短信需要检验发送次数
        if (!isValidCode) {
            return;
        }
        String smsWhitelist = shopConfig.getSmsWhitelist();
        String code = "";
        if (ObjectUtil.isNotEmpty(smsWhitelist) && matches(mobile, Arrays.asList(smsWhitelist.split(",")))){
            code = "123456";
        } else {
            LambdaQueryWrapper<SmsLog> queryWrapper =  new LambdaQueryWrapper<SmsLog>();
            queryWrapper.gt(SmsLog::getRecDate, DateUtil.beginOfDay(new Date()))
                    .lt(SmsLog::getRecDate, DateUtil.endOfDay(new Date()))
                    .eq(SmsLog::getType, sendType.getValue())
                    .orderByDesc(SmsLog::getRecDate);
            if(Objects.isNull(userId)){
                queryWrapper.isNull(SmsLog::getUserId);
            }else{
                queryWrapper.eq(SmsLog::getUserId, userId);
            }
            List<SmsLog> smsLogList = smsLogMapper.selectList(queryWrapper);
            if (smsLogList.size() >= TODAY_MAX_SEND_VALID_SMS_NUMBER) {
                // 今日发送短信验证码次数已达到上限
                throw new YamiShopBindException("yami.verification.limit");
            }

            if (smsLogList.size() > INDEX){
                SmsLog smsLogLast = smsLogList.get(INDEX);
                long currentTimeMillis = System.currentTimeMillis();
                long timeDb = DateUtil.offsetSecond(smsLogLast.getRecDate(), 60).getTime();
                if (currentTimeMillis < timeDb){
                    // 一分钟内只能发送一次验证码
                    throw new YamiShopBindException("yami.verification.time.check");
                }
            }
            code = RandomUtil.randomNumbers(6);
        }
        // 将上一条验证码失效
        smsLogMapper.invalidSmsByMobileAndType(mobile, sendType.getValue());
        params.put("code", code);
    }

    @Override
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public boolean checkValidCode(String mobile, String code, SendType sendType) {
        long checkValidCodeNum = RedisUtil.incr(CHECK_VALID_CODE_NUM_PREFIX + mobile, 1);
        // 半小时后失效
        RedisUtil.expire(CHECK_VALID_CODE_NUM_PREFIX + mobile, 1800);
        if (checkValidCodeNum >= TIMES_CHECK_VALID_CODE_NUM) {
            // 验证码校验过频繁，请稍后再试
            throw new YamiShopBindException("yami.user.code.check.too.much");
        }

        SmsLog dbSms = smsLogMapper.selectOne(new LambdaQueryWrapper<SmsLog>()
                .eq(SmsLog::getUserPhone, mobile)
                .eq(SmsLog::getMobileCode, code)
                .eq(SmsLog::getStatus, 1)
                .eq(SmsLog::getType, sendType.getValue()));
        // 没有找到当前的验证码
        if (dbSms == null) {
            RedisUtil.decr(CHECK_VALID_CODE_NUM_PREFIX + mobile, 1);
            return false;
        }
        RedisUtil.del(CHECK_VALID_CODE_NUM_PREFIX + mobile);
        // 标记为失效状态
        dbSms.setStatus(0);
        smsLogMapper.updateById(dbSms);
        // 验证码已过期
        DateTime offsetMinute = DateUtil.offsetMinute(dbSms.getRecDate(), 5);
        if (offsetMinute.getTime() < System.currentTimeMillis()) {
            RedisUtil.incr(CHECK_VALID_CODE_NUM_PREFIX + mobile, 1);
            return false;
        }
        return true;
    }

    @Override
    public Boolean sendMsgSms(String userMobile , String templateCode , Map<String, String> smsParam) {
//        return sendSms(userMobile, templateCode, smsParam);
        return getSmsServer().sendSms(userMobile, templateCode, smsParam);
    }

    private Boolean sendSms(String mobile, String templateCode, Map<String, String> params) {
        ALiDaYu aLiDaYu = shopConfig.getAliDaYu();

        //初始化acsClient,暂不支持region化
        IClientProfile profile = DefaultProfile.getProfile(REGION_ID, aLiDaYu.getAccessKeyId(), aLiDaYu.getAccessKeySecret());
        IAcsClient acsClient = new DefaultAcsClient(profile);

        //组装请求对象-具体描述见控制台-文档部分内容

        CommonRequest request = new CommonRequest();
        request.setSysMethod(MethodType.POST);
        request.setSysDomain(DOMAIN);
        request.setSysVersion(VERSION);
        request.setSysAction(ACTION);
        request.putQueryParameter("RegionId", REGION_ID);
        //必填:待发送手机号
        request.putQueryParameter("PhoneNumbers", mobile);
        //必填:短信签名-可在短信控制台中找到
        request.putQueryParameter("SignName", aLiDaYu.getSignName());
        //必填:短信模板-可在短信控制台中找到
        request.putQueryParameter("TemplateCode", templateCode);
        request.putQueryParameter("TemplateParam", Json.toJsonString(params));

        try {
            CommonResponse response = acsClient.getCommonResponse(request);
            log.info(response.getData());
            return true;
        } catch (ClientException e) {
            log.error("发送短信失败，请稍后再试："+e.getMessage());
            return false;
        }
    }

    private String formatContent(String content, Map<String, String> params) {
        for (Entry<String, String> element : params.entrySet()) {
            content = content.replace("${" + element.getKey() + "}", element.getValue());
        }
        return content;
    }

    //获取短信发送服务
    private SmsServer getSmsServer(){
        if(shopConfig.isOpenSms(Constant.TENCENTSMS_CONFIG)){
            return tenecntSmsServerImpl;
        }else if(shopConfig.isOpenSms(Constant.ALIDAYU_CONFIG)){
            return aliDayuSmsServerImpl;
        }
        throw new YamiShopBindException("没有开启的短信服务");
    }

    /**
     * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串
     *
     * @param str 指定字符串
     * @param strs 需要检查的字符串数组
     * @return 是否匹配
     */
    private  boolean matches(String str, List<String> strs) {
        if (ObjectUtil.isEmpty(str) || ObjectUtil.isEmpty(strs))
        {
            return false;
        }
        for (String pattern : strs)
        {
            if (isMatch(pattern, str))
            {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断url是否与规则配置:
     * ? 表示单个字符;
     * * 表示一层路径内的任意字符串，不可跨层级;
     * ** 表示任意层路径;
     *
     * @param pattern 匹配规则
     * @param url 需要匹配的url
     * @return
     */
    private boolean isMatch(String pattern, String url)
    {
        AntPathMatcher matcher = new AntPathMatcher();
        return matcher.match(pattern, url);
    }
}
